/* Copyright (c) 2025 Beijing Semidrive Technology Corporation
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "arch_irq.h"
#include "arm_gic.h"
#include "OsIf.h"
#include "irq.h"
#include "irq_num.h"
#if defined(CFG_EN_FUSALIB) && !defined(CFG_ARM_VIC)
#include "FSMR_Monitor.h"
#include "FuSaLib_MonitorConfig.h"
#endif /* defined(CFG_EN_FUSALIB) && !defined(CFG_ARM_VIC) */
/* irq info type */
struct irq_info_s
{
    irq_handler handler; /* Address of the interrupt handler */
    void *arg;           /* The argument provided to the interrupt handler. */
};

struct irq_table
{
    /* gic max int num: IRQ_MAX_INTR_NUM, reserve some SPI for inter-nucleus interrupts */
    struct irq_info_s irq_info[GIC_INTR_MAX_NUM + 1];
} __CACHE_ALIGN;

#if defined(ENABLE_IRQ_TEST) /* only for test */
#define MAX_NEXT_DEEP   (16u)
#include "Mcal_Delay.h"
#include "debug.h"
static uint8   nest[8];
static uint32  full_irq[8];
static uint32  entry_point[8][MAX_NEXT_DEEP],exit_point[8][MAX_NEXT_DEEP];
/* only 32bit unsigned type can use */
#define ABS_DIFF_CYCLE(a,b)   ((uint32)(((uint32)(a))-((uint32)(b))))
#endif
/* int table */
static struct irq_table g_irq_table[CPU_MAX_CORE_NUMBER];
static int irq_handle(uint32 irq)
{
    if (irq < GIC_INTR_MAX_NUM)
    {
        int core_id = Mcal_GetCpuIndex();
        struct irq_info_s *irq_info = &g_irq_table[core_id].irq_info[irq];
        if (irq_info->handler)
        {
#if defined(ENABLE_IRQ_TEST)
            /* detect the nest */
            full_irq[core_id] = irq;
            nest[core_id]++;
            entry_point[core_id][nest[core_id]-1] = read_pmccntr();
#endif
#if defined(NON_AUTOSAR_STANDARD_IRQ_HANDLER)
            return irq_info->handler(irq, irq_info->arg);
#else
            irq_info->handler();
#endif
#if defined(ENABLE_IRQ_TEST)
            uint32 cpsr = arch_irq_save();
            exit_point[core_id][nest[core_id]-1] = read_pmccntr();
            nest[core_id]--;
            uint32 time_diff = ABS_DIFF_CYCLE(exit_point[core_id][nest[core_id]],entry_point[core_id][nest[core_id]]);
            arch_irq_restore(cpsr);
            uint32 time = Mcal_TicksToUs(time_diff);
            /* if full_irq not eq to current irq ,time not*/
            if (irq == full_irq[core_id])
            {
                if(time > 50)
                {
                    PRINT("Oops:Isr too long (%d):(%dus)\n",irq,time);
                }
            }
#endif
        }
    }

    return SDRV_GIC_STATUS_INVALID_PARAM;
}

/**
 * @brief Initialize interrupt controller.
 *
 * This function is used to initialize controller by the main core,
 * if the interrupt controller is gic.
 *
 * @param [in] base base address.
 * @param [in] intr_num number of interrupts.
 * @return SDRV_GIC_STATUS_SUCCESS : initialized success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_initialize(uint32 base, uint32 intr_num)
{
    /* avoid compiler error */
    base = base;
    intr_num = intr_num;

    if (arm_gic_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }
    /* enable the cpu interrupt */
    arch_irq_enable();

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief Initialize interrupt controller secondary.
 *
 *  This function is used to initialize gic except for main core
 *
 * @param [in] base base address.
 * @param [in] intr_num number of interrupts.
 * @return SDRV_GIC_STATUS_SUCCESS : initialized success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_initialize_secondary(uint32 base, uint32 intr_num)
{
    /* avoid compiler error */
    base = base;
    intr_num = intr_num;

    if (arm_gic_secondary_init()) {
        return SDRV_GIC_STATUS_BUSY;
    }
    /* enable the cpu interrupt */
    arch_irq_enable();
    return SDRV_GIC_STATUS_SUCCESS;
}


/**
 * @brief Interrupt attach.
 *
 * @param [in] irq IRQ number.
 * @param [in] handler Pointer to function.
 * @arg [in] arg parameter of handler.
 * @return SDRV_GIC_STATUS_SUCCESS : attach success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_attach(uint32 irq, irq_handler handler, void *arg)
{
    if (irq >= GIC_INTR_MAX_NUM)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    if (NULL == handler)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    int core_id = Mcal_GetCpuIndex();
    struct irq_info_s *irq_info = &g_irq_table[core_id].irq_info[irq];

    IRQ_SAVE

    irq_info->handler = handler;
    irq_info->arg = arg;

    arm_gic_irq_set_priority(irq, DEFAULT_IRQ_PRIORITY);

    IRQ_RESTORE

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief Interrupt datach.
 *
 * @param [in] irq IRQ number.
 * @return SDRV_GIC_STATUS_SUCCESS : detach success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_detach(uint32 irq)
{
    int ret = SDRV_GIC_STATUS_SUCCESS;
    if (irq >= GIC_INTR_MAX_NUM)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    int core_id = Mcal_GetCpuIndex();
    struct irq_info_s *irq_info = &g_irq_table[core_id].irq_info[irq];

    IRQ_SAVE

    if (arm_gic_irq_disable(irq)) {
        ret = SDRV_GIC_STATUS_BUSY;
    }

    irq_info->handler = NULL;
    irq_info->arg = NULL;

    IRQ_RESTORE

    return ret;
}

/**
 * @brief Interrupt disptach.
 *
 * @return SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 *         other return value : according to user callback
 */
int irq_dispatch(void)
{
    int ret;

    uint32 irq = arm_gic_irq_ack();

    if (irq >= GIC_INTR_MAX_NUM)
    {
        ret = SDRV_GIC_STATUS_FAIL;
    }
    else
    {
#if defined (CFG_GIC_PREEPT)
        arch_irq_enable();
        ret = irq_handle(irq);
        arch_irq_disable();
#else
        ret = irq_handle(irq);
#endif /* #if defined (CFG_GIC_PREEPT) */
    }

    arm_gic_irq_eoi(irq);

    return ret;
}

/**
 * @brief irq enable.
 *
 * @param [in] irq irq number.
 * @return SDRV_GIC_STATUS_SUCCESS : enable success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_enable(uint32 irq)
{
    if (irq >= GIC_INTR_MAX_NUM)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }
    else
    {
        arm_gic_irq_enable(irq);
    }

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief irq disable.
 *
 * @param [in] irq number.
 * @return SDRV_GIC_STATUS_SUCCESS : disable success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_disable(uint32 irq)
{
    int ret = SDRV_GIC_STATUS_SUCCESS;
    if (irq >= GIC_INTR_MAX_NUM)
    {
        ret = SDRV_GIC_STATUS_INVALID_PARAM;
    }
    else
    {
        if (arm_gic_irq_disable(irq)) {
            ret = SDRV_GIC_STATUS_BUSY;
        }
    }

    return ret;
}

/**
 * @brief interrupt set priority mask.
 *
 * This priority mask means that all interrupts whose priority
 * is less than this parameter are masked. A smaller value means
 * the higher priority.
 *
 * @param [in]mask int priority mask.
 * @return mask old int priority mask.
 */
uint32 irq_mask(uint32 mask)
{
    return arm_gic_irq_mask(mask);
}

/**
 * @brief interrupt unmask all int.
 */
void irq_unmask(void)
{
    arm_gic_irq_unmask();
}

/**
 * @brief set irq priority.
 *
 * A smaller value means the higher priority.
 *
 * @param [in] irq irq number.
 * @param [in] priority irq priority.
 * @return SDRV_GIC_STATUS_SUCCESS : set priority success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_set_priority(uint32 irq, uint32 priority)
{
    if (irq >= GIC_INTR_MAX_NUM)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    if (priority > GIC_PRI_MAX)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    arm_gic_irq_set_priority(irq, priority);

    return SDRV_GIC_STATUS_SUCCESS;
}

/**
 * @brief get irq priority.
 *
 * @param [in] irq irq number.
 * @return int irq priority.
 * @return SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 *         other return value : irq priority.
 */
int irq_get_priority(uint32 irq)
{
    if (irq >= GIC_INTR_MAX_NUM)
    {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    return arm_gic_irq_get_priority(irq);
}

/**
 * @brief interrupt get current int priority.
 *
 * @return int priority.
 */
uint32 irq_get_current_priority(void)
{
    return arm_gic_irq_get_current_priority();
}

uint32 irq_raise_sgi(unsigned int sgi_id, uint16 target_list)
{
    return arm_gic_raise_sgi(sgi_id, target_list);
}

/**
 * @brief set spi affinity.
 *
 * @param [in] target target core.
 * @param [in] spi_id spi number.
 * @return SDRV_GIC_STATUS_SUCCESS : success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_set_spi_affinity(uint32 target, uint32 spi_id)
{
    return arm_gic_set_affinity(target, spi_id);
}

/**
 * @brief set interrupt as group0 or group1
 *
 * @param[in] intid the interrupt id.
 * @param[in] group group0 or group1.
 * @return SDRV_GIC_STATUS_SUCCESS : success.
 *         SDRV_GIC_STATUS_INVALID_PARAM : invalid param.
 */
int irq_set_group_interrupt(uint32 intid, uint16 group)
{
    if (intid >= GIC_INTR_MAX_NUM) {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    return arm_gic_set_group(intid, group);
}

/**
 * @brief enable group0 or group1 interrupt
 *
 *  if the interrupt is SPI, the group parameter must be
 *  IRQ_GROUP0 | IRQ_SPI_GROUP0 or IRQ_GROUP1 | IRQ_SPI_GROUP1.
 *
 *  if the interrupt is SGI/PPI, the group parameter must be
 *  IRQ_GROUP0 or IRQ_GROUP1.
 *
 * @param[in] group the bitmask of IRQ_GROUP0 and IRQ_SPI_GROUP0.
 *                  the bitmask of IRQ_GROUP1 and IRQ_SPI_GROUP1.
 * @return status code of operation.
 */
int irq_enable_group_interrupt(uint16 group)
{
    return arm_gic_enable_group_interrupt(group);
}

/**
 * @brief disable group0 or group1 interrupt
 *
 *  if the interrupt is SPI, the group parameter must be
 *  IRQ_GROUP0 | IRQ_SPI_GROUP0 or IRQ_GROUP1 | IRQ_SPI_GROUP1.
 *
 *  if the interrupt is SGI/PPI, the group parameter must be
 *  IRQ_GROUP0 or IRQ_GROUP1.
 *
 * @param[in] group the bitmask of IRQ_GROUP0 and IRQ_SPI_GROUP0.
 *                  the bitmask of IRQ_GROUP1 and IRQ_SPI_GROUP1.
 * @return status code of operation.
 */
int irq_disable_group_interrupt(uint16 group)
{
    return arm_gic_disable_group_interrupt(group);
}

/**
 * @brief set interrupt priority group
 *
 * The priority is divided into group-priority and sub-priority,
 * and group-priority can be used for preemption.
 *
 * @param[in] group IRQ_GROUP0 or IRQ_GROUP1.
 * @param[in] pri_group priority split point.
 *            IRQ_PRI_5_BITS_GROUP - [7:3] all for group-priority
 *            IRQ_PRI_4_BITS_GROUP - [7:4] for group-priority, [3] for sub-priority
 *            IRQ_PRI_3_BITS_GROUP - [7:5] for group-priority, [4:3] for sub-priority
 *            IRQ_PRI_2_BITS_GROUP - [7:6] for group-priority, [4:3] for sub-priority
 *            IRQ_PRI_1_BITS_GROUP - [7] for group-priority, [6:3] for sub-priority
 *            IRQ_PRI_0_BITS_GROUP - [7:3] for sub-priority
 * @return status code of operation.
 */
int irq_set_priority_group(uint16 group, uint16 pri_group)
{
    return arm_gic_set_priority_group(group, pri_group);
}

/**
 * @brief raise spi
 *
 * @param[in] spi_id spi interrupt id.
 * @return status code of operation.
 */
int irq_raise_spi(uint32 spi_id)
{
    if (spi_id >= GIC_INTR_MAX_NUM) {
        return SDRV_GIC_STATUS_INVALID_PARAM;
    }

    return arm_gic_raise_spi(spi_id);
}


